﻿using Games.Common;
using Games.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace Games.HuaRongDao
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    partial class GamePage : ContentPage
    {
        IDataBase database = DependencyService.Get<IDataBase>();
        Game game;
        GameInfo gameInfo;
        const int CellSpacing = 1;
        const int W = 4, H = 5;
        int cellSize;
        Map map = new Map();
        HrdLevel boxLevel;
        Label door;
        ScrollView view;
        IDictionary<string, Button> buttons;
        internal GamePage(GameInfo gamePage)
        {
            InitializeComponent();
            BindingContext = gameInfo = gamePage;
            game = new Game();
            LoadAsync().Wait();
            database.CreateTable<HrdRecord>();
        }

        async Task LoadAsync()
        {
            var gameArgs = await database.GetFirstOrDefaultAsync<GameArgs>(x => x.GameID == gameInfo.ID).ConfigureAwait(false);
            if(gameArgs != null)
            {
                boxLevel = BaseArgs.Convert<HrdLevel>(gameArgs);
            }
            else
            {
                boxLevel = new HrdLevel()
                {
                    GameID = gameInfo.ID,
                    //Version = gameInfo.Version,
                    Level = map.GetNames().FirstOrDefault()
                };
            }
        }
        async Task SaveArgsAsync()
        {
            await database.SaveAsync(boxLevel.ToGameArgs());
        }
        void OnLayoutSizeChanged(object sender, EventArgs args)
        {
            Layout layout = sender as Layout;
            var layoutWidth = layout.Width;
            var layoutHeight = layout.Height;
            var sizew = (int)((layoutWidth - CellSpacing) / W);
            var sizeh = (int)((layoutHeight - CellSpacing) / H);
            cellSize = Math.Min(sizew, sizeh);
            var layWidth = W * cellSize + CellSpacing;
            var layHeight = H * cellSize + CellSpacing;
            var xMargin = (layoutWidth - layWidth) / 2.0;
            var yMargin = (layoutHeight - layHeight) / 2.0;
            absoluteLayout.Margin = new Thickness(xMargin, yMargin);
            absoluteLayout.WidthRequest = layWidth;
            absoluteLayout.HeightRequest = layHeight;
            //var rect = new Rectangle(xMargin, yMargin, layWidth, layHeight);
            //AbsoluteLayout.SetLayoutBounds(imgArea, rect);
            //AbsoluteLayout.SetLayoutBounds(absoluteLayout, rect);
            game.Init(map.GetMap(boxLevel.Level));
            InitLayout();
        }

        void InitLayout()
        {
            gameInfo.Level = boxLevel.Level;
            gameInfo.StepCount = 0;
            if(absoluteLayout.Children.Count == 0)
            {
                door = new Label
                {
                    BackgroundColor = Color.Transparent,
                    TextColor = Color.White,
                    HorizontalTextAlignment = TextAlignment.Center,
                    VerticalTextAlignment = TextAlignment.Center,
                    FontAttributes = FontAttributes.Bold,
                    Text = "出 口",
                    FontSize = cellSize / 2
                };
                absoluteLayout.Children.Add(door);
                var pan = new PanGestureRecognizer();
                pan.PanUpdated += this.Pan_PanUpdated;
                var index = 0;
                while(absoluteLayout.Children.Count - 1 < game.Blocks.Count)
                {
                    Cell lifeCell = new Cell(index);
                    lifeCell.GestureRecognizers.Add(pan);
                    absoluteLayout.Children.Add(lifeCell);
                    index++;
                }
            }
            else
            {
                door = (Label)absoluteLayout.Children[0];
            }
            foreach(var item in game.Blocks)
            {
                Cell lifeCell = (Cell)absoluteLayout.Children[item.ID + 1];
                lifeCell.SetBlock(item);
                lifeCell.Opacity = 1;
                Rectangle rect = new Rectangle(item.X * cellSize + CellSpacing, item.Y * cellSize + CellSpacing,
                    cellSize * item.Width - CellSpacing, cellSize * item.Height - CellSpacing);
                AbsoluteLayout.SetLayoutBounds(lifeCell, rect);
                lifeCell.Show();
            }
            Rectangle rectdoor = new Rectangle(1 * cellSize + CellSpacing, 4 * cellSize + CellSpacing,
                cellSize * 2 - CellSpacing, cellSize - CellSpacing);
            AbsoluteLayout.SetLayoutBounds(door, rectdoor);
            TimeCounter.Start();
        }

        private double cx, cy;
        private double panx, pany;
        private int minPos, maxPos;
        private Cell last = null;
        private bool? isHorizontal;
        private Cell activeCell = null;
        async void Pan_PanUpdated(object sender, PanUpdatedEventArgs e)
        {
            try
            {
                if(game.IsFinished())
                {
                    return;
                }
                var cell = (Cell)sender;
                if(e.StatusType == GestureStatus.Started)
                {
                    if(activeCell != null)
                    {
                        return;
                    }
                    activeCell = cell;
                    var fromBound = AbsoluteLayout.GetLayoutBounds(activeCell);
                    cx = fromBound.X;
                    cy = fromBound.Y;
                    return;
                }
                else if(e.StatusType == GestureStatus.Completed)
                {
                    if(activeCell == null || activeCell != cell)
                    {
                        return;
                    }
                    if(isHorizontal.Value)
                    {
                        var dx = ((int)panx + (panx > 0 ? cellSize : -cellSize) / 2) / cellSize;
                        game.Move(cell.Block, dx > 0 ? Direction.Right : Direction.Left, Math.Abs(dx));
                    }
                    else if(isHorizontal.Value == false)
                    {
                        var dy = ((int)pany + (pany > 0 ? cellSize : -cellSize) / 2) / cellSize;
                        game.Move(cell.Block, dy > 0 ? Direction.Down : Direction.Up, Math.Abs(dy));
                    }
                    var tox = cell.Block.X * cellSize + CellSpacing;
                    var toy = cell.Block.Y * cellSize + CellSpacing;
                    if(game.IsFinished())
                    {
                        gameInfo.StepCount++;
                        await Move(cell, tox, toy);
                        await Task.WhenAll(Move(cell, tox, toy + cellSize * 2, 250), cell.FadeTo(0, 250));
                        await ShowFinish();
                    }
                    else
                    {
                        if(last != cell)
                        {
                            gameInfo.StepCount++;
                            last = cell;
                        }
                        await Move(cell, tox, toy);
                    }
                    isHorizontal = null;
                    activeCell = null;
                }
                else if(e.StatusType == GestureStatus.Running)
                {
                    if(activeCell == null || activeCell != cell)
                    {
                        return;
                    }
                    var fromBound = AbsoluteLayout.GetLayoutBounds(cell);
                    panx = e.TotalX + fromBound.X - cx;
                    pany = e.TotalY + fromBound.Y - cy;
                    if(Math.Abs(panx) < cellSize / 5 && Math.Abs(pany) < cellSize / 5)
                    {
                        return;
                    }
                    if(!isHorizontal.HasValue)
                    {
                        isHorizontal = Math.Abs(panx) > Math.Abs(pany);
                    }
                    var (min, max) = game.GetRange(cell.Block, isHorizontal.Value);
                    minPos = min * cellSize + CellSpacing;
                    maxPos = max * cellSize + CellSpacing;
                    if(isHorizontal.Value)
                    {
                        var tox = Math.Max(minPos, Math.Min(maxPos, (int)(cx + panx)));
                        await Move(cell, tox, (int)cy);
                    }
                    else
                    {
                        var toy = Math.Max(minPos, Math.Min(maxPos, (int)(cy + pany)));
                        await Move(cell, (int)cx, toy);
                    }
                }
            }
            catch(Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
        Task Move(Cell cell, int tox, int toy, uint length = 125)
        {
            var taskCompletionSource = new TaskCompletionSource<bool>();
            var fromBound = AbsoluteLayout.GetLayoutBounds(cell);
            var fromx = fromBound.X;
            var fromy = fromBound.Y;
            cell.Animate<Rectangle>(
                "Push",
                t => new Rectangle(fromx + t * (tox - fromx), fromy + t * (toy - fromy),
                    fromBound.Width, fromBound.Height),
                r => AbsoluteLayout.SetLayoutBounds(cell, r),
                16,
                length,
                Easing.Linear,
                (r, b) => taskCompletionSource.SetResult(b)
            );
            return taskCompletionSource.Task;
        }

        async Task ShowFinish()
        {
            await SaveArgsAsync();
            var record = await SaveRecordAsync();
            await DisplayAlert(record.Level, record.Message, "OK");
        }
        async Task<Record> SaveRecordAsync()
        {
            var dur = TimeCounter.Stop();
            var cRecord = new HrdRecord()
            {
                Step = gameInfo.StepCount,
                GameID = gameInfo.ID,
                CreateTime = DateTime.Now,
                MinStep = game.MinStep,
                Name = boxLevel.Level,
                Duration = dur
            };

            await database.SaveRecordByLevel<HrdRecord>(cRecord,
                o => o.Name == cRecord.Name,
                o => o.Step > cRecord.Step || (o.Step == cRecord.Step && o.Duration > cRecord.Duration)
            );
            return cRecord.Convert();
        }
        async void LevelClicked(object sender, EventArgs args)
        {
            if(!mask.Contains("levels"))
            {
                (view, buttons) = ControlUtil.GetLevelGrid(this.map.GetNames(), -1, 2, LevelSelect);
                mask.Add("levels", "选择关卡", view);
            }
            mask.ShowWithCancel("levels");
            await view.ScrollToAsync(this.buttons[boxLevel.Level], ScrollToPosition.Center, false);
        }
        async void LevelSelect(string level)
        {
            mask.Hide();
            if(boxLevel.Level != level)
            {
                boxLevel.Level = level;
                await SaveArgsAsync();
            }
            game.Init(map.GetMap(boxLevel.Level));
            InitLayout();
        }
        void RestartClicked(object sender, EventArgs args)
        {
            game.Init(map.GetMap(boxLevel.Level));
            InitLayout();
        }

        void OnAboutButtonClicked(object sender, EventArgs args)
        {
            if(!mask.Contains("about"))
            {
                var aboutView = ControlUtil.GetAbout(gameInfo);
                mask.Add("about", gameInfo.Name, aboutView);
            }
            mask.ShowWithCancel("about", "确定");
        }
        void OnRecordClicked(object sender, EventArgs args)
        {
            var sorter = new Sorter<HrdRecord>(q => q.OrderBy(x => x.Name));
            Navigation.PushAsync(new RecordPage(gameInfo.Name, new RecordViewModel<HrdRecord>(sorter)));
        }

        private void TapToBack(object sender, EventArgs e)
        {
            Navigation.PopAsync();
        }
    }
}